#include <hal/acpi/acpi.h>
#include <mm/valloc.h>
#include <string.h>
#include <mm/mm.h>
#include <lib/math.h>

#define LOG_TAG "ACPI"
#include <log.h>

static acpi_ctx_t* ctx = NULL;

void madt_parse(madt_t* madt, acpi_ctx_t* ctx) {
    ctx->madt.apic_addr = madt->apic_addr;

    uptr ics_start = (uptr)madt + sizeof(madt_t);
    uptr ics_end = (uptr)madt + madt->header.length;

    // Cosidering only one IOAPIC present (max 24 pins)
    ctx->madt.irq_exception = (intso_t**)vzalloc(24 * sizeof(intso_t*));

    size_t so_idx = 0;
    while (ics_start < ics_end) {
        ics_hdr_t* entry = (ics_hdr_t*)ics_start;
        switch (entry->type) {
        case ACPI_MADT_LAPIC:
            ctx->madt.apic = (plas_t*)entry;
            break;
        case ACPI_MADT_IOAPIC:
            ctx->madt.ioapic = (ioas_t*)entry;
            break;
        case ACPI_MADT_INTSO: {
            intso_t* intso_tbl = (intso_t*)entry;
            ctx->madt.irq_exception[intso_tbl->source] = intso_tbl;
            break;
        }
        default:
            break;
        }

        ics_start += entry->length;
    }
}

acpi_ctx_t* acpi_get_context() {
    assert_msg(ctx, "ACPI is not initialized");
    return ctx;
}

int acpi_rsdp_validate(rsdp_t* rsdp) {
    u8 sum = 0;
    u8* rsdp_ptr = (u8*)rsdp;
    for (size_t i = 0; i < 20; i++) {
        sum += *(rsdp_ptr + i);
    }

    return sum == 0;
}

rsdp_t* acpi_locate_rsdp() {
    rsdp_t* rsdp = NULL;

    uptr mem_start = 0x4000;
    for (; mem_start < 0x100000; mem_start += 16) {
        u32 sig_low = *((u32*)(mem_start));
        if (sig_low == ACPI_RSDP_SIG_L) {
            rsdp = (rsdp_t*)(mem_start);
            break;
        }
    }
    return rsdp;
}

void acpi_init() {
    rsdp_t* rsdp = acpi_locate_rsdp();

    assert_msg(rsdp, "Fail to locate ACPI_RSDP");
    assert_msg(acpi_rsdp_validate(rsdp), "Invalid ACPI_RSDP (checksum failed)");

    rsdt_t* rsdt = rsdp->rsdt;

    ctx = valloc(sizeof(acpi_ctx_t));
    assert_msg(ctx, "Fail to create ACPI context");

    strncpy(ctx->oem_id, rsdt->header.oem_id, 6);
    ctx->oem_id[6] = '\0';

    size_t entry_n = (rsdt->header.length - sizeof(sdth_t)) >> 2;
    for (size_t i = 0; i < entry_n; i++) {
        sdth_t* sdthdr =
            (sdth_t*)((plas_t**)&(rsdt->entry))[i];
        switch (sdthdr->signature) {
        case ACPI_MADT_SIG:
            madt_parse((madt_t*)sdthdr, ctx);
            break;
        case ACPI_FADT_SIG:
            LOGW("ACPI-FADT not supported yet\n");
            break;
        case ACPI_MCFG_SIG:
            LOGW("ACPI-MCFG not supported yet\n");
            break;
        default:
            break;
        }
    }

    LOGI("ACPI: %s\n", ctx->oem_id);
}